
#include "SSHClientWrapper.h"
#include "SecurityChain.h"
#include "SecurityTokenKey.h"
#include "RemoteSSHKeyFile.h"

/** Given an SSH client, key updates should be administered.  This
 * wrapper function does this.
 * 
 * @param isshcli The SSH client whose keys are to be updated.
 * 
 * @param inetdb The network database for finding and changing the
 * keys which are to be used to access this server.
 * 
 * @param ikeydb The key database for loading the appropriate keys
 * from.
 * 
 * @param ikeytag The key tag that identifies keys as belonging to the
 * system, and requires that we manage them specifically.
 * 
 * @param iusername The delegated SSH actor ID that owns all the
 * necessary keys.
 * 
 * @param isshusername The user name used when logging in to the SSH
 * server.
 * 
 * @param ihostname The remote host name of the SSH server, for
 * looking up database entries (so always remote and unique).
 * 
 * @param isecretkeypwd The key for liberating secret keys fro the
 * database on behalf of the real user. */
void SSHClientWrapper::doRemoteKeyUpdates(SSHClient *isshcli, NetworkDB *inetdb, KeyDB *ikeydb,
   const char *ikeytag, const char *iusername, const char *isshusername, const char *ihostname,
   const char *isecretkeypwd)
{
   if (isshcli == NULL)
   {
      errprint("can't do remote key updates without an SSH client connection (is NULL)");
      return;
   }
   if (inetdb == NULL)
   {
      errprint("can't do remote key updates without access to the network database (is NULL)");
      return;
   }
   if (ikeydb == NULL)
   {
      errprint("can't do remote key updates without access to the key database (is NULL)");
      return;
   }
   if (ikeytag == NULL)
   {
      errprint("key management tag is NULL - aborting remote key updates");
      return;
   }
   if (iusername == NULL)
   {
      errprint("can't update keys with NULL username - can't look up network info");
      return;
   }
   if (isshusername == NULL)
   {
      errprint("can't update SSH credentials in database with NULL username for this SSH connection");
      return;
   }
   if (ihostname == NULL)
   {
      errprint("can't update SSH credentials in database with NULL hostname for this SSH connection");
      return;
   }
   if (isecretkeypwd == NULL)
   {
      errprint("secret password for storing SSH keys is NULL and so is probably insecure");
   }
   RemoteSSHKeyFile *remote_keyfile = new RemoteSSHKeyFile(*isshcli, *inetdb, *ikeydb, ikeytag);
   char *no_extens = strdup(iusername);
   no_extens[strlen(iusername) - 4] = '\0';
   char *ssh_owner = StringUtils::varprintf("%s", iusername);
   char *latest_sshkey = ikeydb->checkingTable()->getLatestKeyNameOfOwner(ssh_owner);
   char *using_sshkey = NULL;
   if (inetdb->sqlCheckTableExists("%s_credentials", iusername))
      using_sshkey = inetdb->getKeyNameNeededForHostLogin(no_extens, ihostname);
   if (using_sshkey)
   {
      if (strcmp(using_sshkey, latest_sshkey) != 0)
      {
         // decrement the usedrefcount of the old key
         if (ikeydb->signingTable()->testKeyExists(using_sshkey))
            ikeydb->signingTable()->decrementKeyUsedReferenceCount(using_sshkey);
         if (ikeydb->checkingTable()->testKeyExists(using_sshkey))
            ikeydb->checkingTable()->decrementKeyUsedReferenceCount(using_sshkey);
         // increment the usedrefcount of the new key
         if (ikeydb->signingTable()->testKeyExists(latest_sshkey))
            ikeydb->signingTable()->incrementKeyUsedReferenceCount(latest_sshkey);
         if (ikeydb->checkingTable()->testKeyExists(latest_sshkey))
            ikeydb->checkingTable()->incrementKeyUsedReferenceCount(latest_sshkey);
         // key to use needs updating in the network database user
         // table
         inetdb->updateHostLoginToUseNewKey(no_extens, ihostname, latest_sshkey);
         // remote keyfile add
         remote_keyfile->addKey(latest_sshkey, isecretkeypwd);
      }
      // else doesn't need update
   } else
   {
      // need to create a login in the network database for this host
      if (!(inetdb->addUserCredentials(no_extens, ihostname, isshusername, latest_sshkey)))
      {
         // increment the usedrefcount of the new key
         if (ikeydb->signingTable()->testKeyExists(latest_sshkey))
            ikeydb->signingTable()->incrementKeyUsedReferenceCount(latest_sshkey);
         if (ikeydb->checkingTable()->testKeyExists(latest_sshkey))
            ikeydb->checkingTable()->incrementKeyUsedReferenceCount(latest_sshkey);
         // remote keyfile add
         remote_keyfile->addKey(latest_sshkey, isecretkeypwd);
      }
   }
   if (using_sshkey) free(using_sshkey);
   if (latest_sshkey) free(latest_sshkey);
   if (ssh_owner) free(ssh_owner);
   if (no_extens) free(no_extens);
   // should refesh the keys, commit and free
   delete remote_keyfile;
}

/** Most SSH accesses are done via SSH server that have been added to
 * the system. When the SSH server has been added to the system, a
 * relatively simplified access procedure can be used, which should be
 * callable relatively easily from many places in the code. Hence this
 * wrapper function. Although this function is ONLY for immediately,
 * locally accessible servers.
 * 
 * @param ihostname Remote host name for the SSH server (can be
 * 127.0.0.1 if this is a forwarded/telescoped connection).
 * 
 * @param iportno Remote port number for the SSH server (can be a high
 * port number for forwarded/telescoped connections).
 * 
 * @param imp Memory pool for APR functions.
 * 
 * @param ikeyprint Key fingerprint for the remote host (binary, not
 * hexadecimal).
 * 
 * @param ikeyprintlen Key fingeprint length (of binary string).
 * 
 * @param irealservername The real remote server name, so if it is a
 * forwarding, give the server host name that it is being forwarded
 * to.
 * 
 * @param irealserverport The real remote server port, so if it is a
 * forwarding, give the port number that it is being forwarded to on
 * the final port.
 * 
 * @param ipendingdb If given, it will give the code the option of
 * adding failed host key fingerprints to the database of pending
 * changes, so that these changes can be approved by a local
 * administrator through the security console.
 * 
 * @param iusername The delegated SSH actor ID that owns all the SSH
 * keys.
 * 
 * @param inetdb The network database detailing knowledge of the
 * program about the network.
 * 
 * @param ikeydb The key database to get keys from to authenticate
 * (and to update keys if necessary).
 * 
 * @param isecretkeypwd The secret key password required to liberate
 * secret keys from the key database, so that they may be used to
 * access SSH servers.
 * 
 * @return The SSH client class with a (hopefully) functioning SSH
 * server. May return NULL if the SSH client did not come up (didn't
 * work properly). */
SSHClient *SSHClientWrapper::sshClientDefault(const char *ihostname, int iportno, apr_pool_t *imp,
   const char *ikeyprint, int ikeyprintlen, const char *irealservername, int irealserverport,
   PendingDB *ipendingdb, const char *iusername, NetworkDB *inetdb, KeyDB *ikeydb, const char *isecretkeypwd)
{
   char *my_sshusername = inetdb->getSSHUsername(iusername, irealservername);
   if (my_sshusername == NULL)
   {
      errprint("trying to access a host without using any recorded credentials!");
      return NULL;
   }
   char *my_keyname = inetdb->getKeyNameNeededForHostLogin(iusername, irealservername);
   SecurityChain *secur_chain = new SecurityChain();
   SecurityTokenKey *secur_tok_key = new SecurityTokenKey(my_keyname, my_keyname, isecretkeypwd, ikeydb);
   secur_chain->addSecurityToken(secur_tok_key);
   free(my_keyname);
   // should be getting the key fingerprint here from the database
   // also should be passing the pendingdb to receive key fingerprint
   // changes
   SSHClient *new_client = new SSHClient(ihostname, iportno, imp, ikeyprint, ikeyprintlen, irealservername, irealserverport, ipendingdb);
   new_client->useSecurityChain(secur_chain, my_sshusername);
   if (new_client->isUp())
   {
      // check for any key updates needed
      doRemoteKeyUpdates(new_client, inetdb, ikeydb, APP_KEY_TAG, iusername, my_sshusername, irealservername, isecretkeypwd);
   } else
   {
      errprint("failed to connect to '%s:%d'", irealservername, irealserverport);
      delete new_client;
      new_client = NULL;
   }
   free(my_sshusername);
   delete secur_chain;
   return new_client;
}
